package xencode

import (
	"encoding/binary"
)

func littleEndianUint32(str []byte, appendLength bool) []uint32 {
	esLen := (len(str)-1)>>2 + 1
	ret := make([]uint32, esLen, esLen+1)
	if appendLength {
		ret = append(ret, uint32(len(str)))
	}
	rest := len(str) & 3
	qual := len(str) - rest
	i := 0
	for ; i < qual; i += 4 {
		ret[i>>2] = binary.LittleEndian.Uint32(str[i : i+4])
	}
	for j := 0; j < rest; j++ {
		ret[i>>2] |= (uint32(str[i+j]) << (8 * j))
	}
	return ret
}

func littleEndianPutUint32(arr []uint32) []byte {
	res := make([]byte, len(arr)<<2)
	for i := range arr {
		binary.LittleEndian.PutUint32(res[i<<2:], arr[i])
	}
	return res
}

func XEncode(str []byte, key []byte) []byte {
	if str == nil {
		return nil
	}
	var (
		v = littleEndianUint32(str, true)
		k = littleEndianUint32(key, false)
		n = uint32(len(v) - 1)
		z = v[n]
		y = v[0]
		c = uint32(0x86014019 | 0x183639A0)
		m uint32
		e uint32
		p uint32
		q = 6 + 52/(n+1)
		d = uint32(0)
	)
	for ; 0 < q; q-- {
		d = d + c //&0xFFFFFFFF
		e = d >> 2 & 3
		for p = 0; p < n; p++ {
			y = v[p+1]
			m = z>>5 ^ y<<2
			m += (y>>3 ^ z<<4) ^ (d ^ y)
			m += k[(p&3)^e] ^ z
			z = v[p] + m // &0xFFFFFFFF
			v[p] = z
		}
		y = v[0]
		m = z>>5 ^ y<<2
		m += (y>>3 ^ z<<4) ^ (d ^ y)
		m += k[(p&3)^e] ^ z
		z = v[n] + m // &0xFFFFFFFF
		v[n] = z
	}
	return littleEndianPutUint32(v)
}
